#include "buffs.h"
#include "random.h"
#include "actor.h"
#include "item.h"
#include "creature.h"
#include "message.h"

StatsBuff::StatsBuff( int hpMax, int mpMax, int sStr, int sDex, int sInt,
                      int ac, int magicRes, int fireRes )
: _hpMax( hpMax )
, _mpMax( mpMax )
, _sStr( sStr )
, _sDex( sDex )
, _sInt( sInt )
, _ac( ac )
, _magicRes( magicRes )
, _fireRes( fireRes )
{
}

bool StatsBuff::Stats( int* hpMax, int* mpMax, int* sStr, int* sDex, int* sInt,
                       int* ac, int* magicRes, int* fireRes ) const
{
    PRE_ACTOR;
    *hpMax += _hpMax;
    *mpMax += _mpMax;
    *sStr += _sStr;
    *sDex += _sDex;
    *sInt += _sInt;
    *ac   += _ac;
    *magicRes += _magicRes;
    *fireRes  += _fireRes;
    return true;
}

DerivedStatsBuff::DerivedStatsBuff( double hpRegen, double mpRegen, int weightMax )
: _hpRegen( hpRegen )
, _mpRegen( mpRegen )
, _weightMax( weightMax )
{
}

bool DerivedStatsBuff::DerivedStats( double* hpRegen, double* mpRegen, int* weightMax ) const
{
    PRE_ACTOR;
    *hpRegen   += _hpRegen;
    *mpRegen   += _mpRegen;
    *weightMax += _weightMax;
    return true;
}

OnFireBuff::OnFireBuff()
: Buff( STACK_ON_FIRE, 1 )
{
    SetPriority( 1 );
}

bool OnFireBuff::OnTick()
{
    Buff::OnTick();
    if ( IsDestroyed() )
        return true;
    POST_ACTOR;
    // Set fire to items in inventory
    ItemList t;
    if ( GetTarget() != 0 && GetTarget()->Call< BIInventory >( &t ) ) {
        foreach ( Item* it, t ) {
            if ( Random::Int( 500 ) == 0 )
                it->Call< BIOnSetFire >();
        }
    }
    // Set fire to items in carrier's inventory (but not to/from wielded ones)
    Creature* carrier;
    bool worn;
    bool wielded;
    if ( GetTarget() != 0 && GetTarget()->Call< BIItemCarrier >( &carrier, &worn, &wielded ) && carrier && !wielded && !Random::Int( 150 ) ) {
        ItemList t;
        carrier->GetItemsInInventory( t );
        for ( uint i = 0; i < t.size(); ++i ) {
            if ( t[ i ]->IsWielded() ) {
                t.erase( t.begin() + i );
                --i;
            }
        }
        if ( !t.empty() )
            t[ Random::Int( t.size() ) ]->Call< BIOnSetFire >();
    }
    // Set fire to carrier if worn
    if ( carrier && worn && !Random::Int( 200 ) )
        carrier->Call< BIOnSetFire >();
    // Set fire to stuff nearby
    Actor* actor = 0;
    if ( GetTarget()->Call< BIIsActor >( &actor ) && actor != 0 ) {
        ActorList list;
        actor->GetAdjacentActors( list );
        foreach ( Actor* a, list ) {
            if ( ( Random::Int( 500 ) == 0 && a->GetX() == actor->GetX() && a->GetY() == actor->GetY() ) || ( Random::Int( 500 ) == 0 && !dynamic_cast< Creature* >( a ) ) )
                a->Call< BIOnSetFire >();
        }
    }
    return true;
}

bool OnFireBuff::Priority( int* priority ) const
{
    *priority += 100;
    return true;
}

bool OnFireBuff::Fire( uint* fireType ) const
{
    PRE_ACTOR;
    *fireType |= FIRE_ON_FIRE;
    return true;
}

bool OnFireBuff::Name( std::string* name, bool* plural, uint article ) const
{
    PRE_ACTOR;
    if ( name->empty() && article == ARTICLE_INDEFINITE )
        *name += "a ";
    *name += "burning ";
    return true;
}

bool OnFireBuff::Light( int* lightRange ) const
{
    PRE_ACTOR;
    uint flammable;
    GetTarget()->Call< BIFlammable >( &flammable );
    if ( flammable & FLAMMABLE_BURN_BRIGHT )
        *lightRange = std::max( *lightRange, int( MED_FIRE_RADIUS ) );
    else
        *lightRange = std::max( *lightRange, int( SMALL_FIRE_RADIUS ) );
    return true;
}

bool OnFireBuff::StatusEffects( StatusEffectList* list ) const
{
    PRE_ACTOR;
    list->push_back( StatusEffect( "On fire", STATUS_BAD ) );
    return true;
}

bool OnFireBuff::Symbol( IO::SymList* list ) const
{
    POST_ACTOR;
    if ( list->size() == 1 )
        list->push_back( IO::Sym( Sym::DOUBLEEXCLAMATION, Colour( 255, 0, 0 ) ) );
    else for ( uint i = 0; i < list->size(); i += 2 ) {
        ( *list )[ i ].first = Sym::DOUBLEEXCLAMATION;
        ( *list )[ i ].second = Colour( 255, 0, 0 );
    }
    return true;
}

bool OnFireBuff::CreateWearBuff( CompoundBuff** buff ) const
{
    PRE_ACTOR;
    uint flammable;
    GetTarget()->Call< BIFlammable >( &flammable );
    if ( flammable & FLAMMABLE_BURN_BRIGHT )
        ( *buff )->AddBuff( new LightRadiusBuff( BIG_FIRE_RADIUS ) );
    else
        ( *buff )->AddBuff( new LightRadiusBuff( SMALL_FIRE_RADIUS ) );
    return true;
}

bool OnFireBuff::CreateWieldBuff( CompoundBuff** buff ) const
{
    PRE_ACTOR;
    uint flammable;
    GetTarget()->Call< BIFlammable >( &flammable );
    if ( flammable & FLAMMABLE_BURN_BRIGHT )
        ( *buff )->AddBuff( new LightRadiusBuff( BIG_FIRE_RADIUS ) );
    else
        ( *buff )->AddBuff( new LightRadiusBuff( SMALL_FIRE_RADIUS ) );
    return true;
}

bool OnFireBuff::CreateHoldBuff( CompoundBuff** buff ) const
{
    PRE_ACTOR;
    ( *buff )->AddBuff( new LightRadiusBuff( MED_SMOULDER_RADIUS ) );
    return true;
}

bool OnFireBuff::OnSetFire()
{
    PRE_ACTOR;
    return false;
}

bool OnFireBuff::Flammable( uint* flammableType ) const
{
    PRE_ACTOR;
    *flammableType |= FIRE_SOURCE_UNLIMITED;
    return true;
}

bool OnFireBuff::OnLight( Creature* c, Behaved* target )
{
    PRE_ACTOR;
    GetTarget()->Bank().TernarySwitch( TERNARY_ALIGHT_FROM_BURNING_OBJECT, c, target, GetTarget() );
    target->Call< BIOnSetFire >();
    return false;
}

void OnFireBuff::OnBuffApply()
{
    uint flammable;
    GetTarget()->Call< BIFlammable >( &flammable );
    GetTarget()->Bank().Unary( flammable & FLAMMABLE_BURN_BRIGHT ? UNARY_FIRE_CATCH_FIRE_BRIGHT : UNARY_FIRE_CATCH_FIRE, GetTarget() );
}

void OnFireBuff::OnBuffExpire()
{
    uint flammable;
    GetTarget()->Call< BIFlammable >( &flammable );
    if ( flammable & FLAMMABLE_CHAR )
        ( new CharredBuff() )->SetTarget( GetTarget() );

    if ( flammable & FLAMMABLE_SMOULDER_LONG ) {
        Buff* b = new SmoulderingBuff();
        b->StartTimer( Random::Int( LONG_DICE ) );
        b->SetTarget( GetTarget() );
        return;
    }
    if ( flammable & FLAMMABLE_SMOULDER_SHORT ) {
        Buff* b = new SmoulderingBuff();
        b->StartTimer( Random::Int( SHORT_DICE ) );
        b->SetTarget( GetTarget() );
        return;
    }

    if ( flammable & FLAMMABLE_DESTROY ) {
        if ( !GetTarget()->IsDestroyed() )
            GetTarget()->Bank().Unary( UNARY_FIRE_DESTROY_EVENTUAL, GetTarget() );
        GetTarget()->Destroy();
    }
    else
        GetTarget()->Bank().Unary( UNARY_FIRE_GO_OUT, GetTarget() );
}

SmoulderingBuff::SmoulderingBuff()
: Buff( STACK_ON_FIRE, 1 )
{
    SetPriority( 1 );
}

bool SmoulderingBuff::Priority( int* priority ) const
{
    *priority += 10;
    return true;
}

bool SmoulderingBuff::Fire( uint* fireType ) const
{
    PRE_ACTOR;
    *fireType |= FIRE_SMOULDERING;
    return true;
}

bool SmoulderingBuff::Name( std::string* name, bool* plural, uint article ) const
{
    PRE_ACTOR;
    if ( name->empty() && article == ARTICLE_INDEFINITE )
        *name += "a ";
    *name += "smouldering ";
    return true;
}

bool SmoulderingBuff::Light( int* lightRange ) const
{
    PRE_ACTOR;
    uint flammable;
    GetTarget()->Call< BIFlammable >( &flammable );
    if ( flammable & BIFlammable::FLAMMABLE_BURN_BRIGHT )
        *lightRange = std::max( *lightRange, int( MED_SMOULDER_RADIUS ) );
    else
        *lightRange = std::max( *lightRange, int( SMALL_SMOULDER_RADIUS ) );
    return true;
}

bool SmoulderingBuff::StatusEffects( StatusEffectList* list ) const
{
    PRE_ACTOR;
    list->push_back( StatusEffect( "Smouldering", STATUS_BAD ) );
    return true;
}

bool SmoulderingBuff::Symbol( IO::SymList* list ) const
{
    POST_ACTOR;
    if ( list->size() == 1 ) {
        list->push_back( IO::Sym( ( *list )[ 0 ].first, ( *list )[ 0 ].second ) );
        Colour& c = ( *list )[ 1 ].second;
        c.r = uchar( std::min( 255, int( double( c.r ) * 1.8 ) ) );
        return true;
    }
    for ( uint i = 0; i < list->size(); ++i ) {
        Colour& c = ( *list )[ i ].second;
        if ( i % 2 != 0 )
            c.r = uchar( std::min( 255, int( double( c.r ) * 1.8 ) ) );
    }
    return true;
}

bool SmoulderingBuff::CreateWearBuff( CompoundBuff** buff ) const
{
    PRE_ACTOR;
    uint flammable;
    GetTarget()->Call< BIFlammable >( &flammable );
    if ( flammable & BIFlammable::FLAMMABLE_BURN_BRIGHT )
        ( *buff )->AddBuff( new LightRadiusBuff( BIG_SMOULDER_RADIUS ) );
    else
        ( *buff )->AddBuff( new LightRadiusBuff( SMALL_SMOULDER_RADIUS ) );
    return true;
}

bool SmoulderingBuff::CreateWieldBuff( CompoundBuff** buff ) const
{
    PRE_ACTOR;
    uint flammable;
    GetTarget()->Call< BIFlammable >( &flammable );
    if ( flammable & BIFlammable::FLAMMABLE_BURN_BRIGHT )
        ( *buff )->AddBuff( new LightRadiusBuff( BIG_SMOULDER_RADIUS ) );
    else
        ( *buff )->AddBuff( new LightRadiusBuff( SMALL_SMOULDER_RADIUS ) );
    return true;
}

bool SmoulderingBuff::CreateHoldBuff( CompoundBuff** buff ) const
{
    PRE_ACTOR;
    ( *buff )->AddBuff( new LightRadiusBuff( SMALL_SMOULDER_RADIUS ) );
    return true;
}

bool SmoulderingBuff::OnSetFire()
{
    PRE_ACTOR;
    uint flammable;
    GetTarget()->Call< BIFlammable >( &flammable );
    if ( flammable & BIFlammable::FLAMMABLE_SMOULDER )
        StartTimer( GetTimer() + Random::Int( 3 ) );
    return false;
}

void SmoulderingBuff::OnBuffApply()
{
    GetTarget()->Bank().Unary( UNARY_FIRE_SMOULDER, GetTarget() );
}

void SmoulderingBuff::OnBuffExpire()
{
    uint flammable;
    GetTarget()->Call< BIFlammable >( &flammable );

    if ( flammable & BIFlammable::FLAMMABLE_DESTROY ) {
        if ( !GetTarget()->IsDestroyed() )
            GetTarget()->Bank().Unary( UNARY_FIRE_DESTROY_EVENTUAL, GetTarget() );
        GetTarget()->Destroy();
    }
    else
        GetTarget()->Bank().Unary( UNARY_FIRE_GO_OUT, GetTarget() );
}

CharredBuff::CharredBuff()
: Buff( STACK_CHARRED, 1 )
{
    SetPriority( -1 );
}

bool CharredBuff::Priority( int* priority ) const
{
    *priority -= 100;
    return true;
}

bool CharredBuff::Fire( uint* fireType ) const
{
    PRE_ACTOR;
    *fireType |= FIRE_CHARRED;
    return true;
}

bool CharredBuff::Name( std::string* name, bool* plural, uint article ) const
{
    PRE_ACTOR;
    if ( name->find( "burning" ) != std::string::npos || name->find( "smouldering" ) != std::string::npos )
        return true;
    if ( name->empty() && article == ARTICLE_INDEFINITE )
        *name += "a ";
    *name += "charred ";
    return true;
}

bool CharredBuff::StatusEffects( StatusEffectList* list ) const
{
    PRE_ACTOR;
    list->push_back( StatusEffect( "Charred", STATUS_BAD ) );
    return true;
}

bool CharredBuff::Symbol( IO::SymList* list ) const
{
    POST_ACTOR;
    for ( uint i = 0; i < list->size(); ++i ) {
        Colour& c = ( *list )[ i ].second;
        c.r = uchar( double( c.r ) / 1.8 );
        c.g = uchar( double( c.g ) / 2.0 );
        c.b = uchar( double( c.b ) / 1.9 );
    }
    return true;
}

bool CharredBuff::CreateWearBuff( CompoundBuff** buff ) const
{
    PRE_ACTOR;
    return false;
}

bool CharredBuff::CreateWieldBuff( CompoundBuff** buff ) const
{
    PRE_ACTOR;
    return false;
}

bool CharredBuff::CreateHoldBuff( CompoundBuff** buff ) const
{
    PRE_ACTOR;
    return false;
}

bool CharredBuff::OnSetFire()
{
    PRE_ACTOR;
    uint flammable;
    GetTarget()->Call< BIFlammable >( &flammable );
    if ( flammable & BIFlammable::FLAMMABLE_SMOULDER_LONG ) {
        Buff* b = new SmoulderingBuff();
        b->StartTimer( Random::Int( LONG_DICE ) );
        b->SetTarget( GetTarget() );
    }
    else if ( flammable & BIFlammable::FLAMMABLE_SMOULDER_SHORT ) {
        Buff* b = new SmoulderingBuff();
        b->StartTimer( Random::Int( SHORT_DICE ) );
        b->SetTarget( GetTarget() );
    }
    return false;
}

void CharredBuff::OnBuffApply()
{
    uint flammable;
    if ( !GetTarget()->Call< BIFlammable >( &flammable ) || flammable & BIFlammable::FLAMMABLE_SMOULDER || flammable & BIFlammable::FLAMMABLE_BURN || flammable & BIFlammable::FLAMMABLE_CHAR )
        return;

    GetTarget()->Bank().Unary( UNARY_FIRE_CHAR, GetTarget() );
}

LightRadiusBuff::LightRadiusBuff( int radius )
: _radius( radius )
{
}

bool LightRadiusBuff::Light( int* lightRange ) const
{
    PRE_ACTOR;
    *lightRange = std::max( *lightRange, _radius );
    return true;
}

UnidentifiedBuff::UnidentifiedBuff()
: Buff( STACK_UNIDENTIFIED, 1 )
{
    SetPriority( -100 );
}

bool UnidentifiedBuff::OnIdentify()
{
    Destroy();
    return true;
}